package ph.com.gs3.formalistics.model.dao;

import java.util.ArrayList;
import java.util.List;

import ph.com.gs3.formalistics.model.tables.UserAccountTable;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.model.valueobjects.business.User.ACTIVITY;
import ph.com.gs3.formalistics.utilities.FLLogger;
import ph.com.gs3.formalistics.utilities.FLLogger.LogType;
import ph.com.gs3.formalistics.utilities.Serializer;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

public class UserAccountsDAO extends SQLiteDataAccessObject {

	public static final String TAG = UserAccountsDAO.class.getSimpleName();

	private String[] tableColumns;

	public static enum PasswordExclusion {
		EXCLUDE_PASSWORD, INCLUDE_PASSWORD
	}

	public UserAccountsDAO(FLDatabaseHelper databaseHelper) {
		super(databaseHelper);
	}

	public UserAccountsDAO(Context context) {
		super(context);

		tableColumns = new UserAccountTable().getTableColumns();
	}

	// ============================================================================
	// Update & Insert Methods

	/**
	 * Checks if a user is already existing in the database, if he is, then that user is
	 * updated, otherwise, a new user is created in the database.
	 * 
	 * @param user
	 *            the user object to be saved.
	 * @return the updated user object, this contains the new generated id if a new user
	 *         is created.
	 * @throws IncompleteUserValuesException
	 */
	public User registerOrUpdateUser(User user) throws IncompleteUserValuesException {

		validateUserForRegistration(user);

		User existingUser = getUserWithWebIdAndServer(user.getWebId(), user.getAccountServer());
		User resultingUser = null;

		// register or update user

		if (existingUser != null) {
			int affectedRows = updateUser(user, user.getWebId(), user.getAccountServer());

			if (affectedRows > 0) {
				resultingUser = getUserWithWebIdAndServer(user.getWebId(), user.getAccountServer());
			} else {
				// TODO: throw exception here
				resultingUser = existingUser;
			}
		} else {
			resultingUser = insertUser(user);
		}

		// check if the registered user is active, if he is, deactivate all other users by
		// call the activate method

		return resultingUser;

	}

	private User insertUser(User user) {

		ContentValues cv = createCVFromUser(user);

		int insertId = (int) database.insert(UserAccountTable.NAME, null, cv);

		// log any possible errors
		if (insertId == -1) {	// if no data is inserted
			FLLogger.log(
			        TAG,
			        LogType.ERROR,
			        "No user data was inserted to database upon registration. User data is: "
			                + cv.toString());
		}

		return getUserWithId(insertId);

	}

	public int updateUser(User newUser, int webId, String server,
	        PasswordExclusion passwordExclusion) {

		ContentValues cv = createCVFromUser(newUser);

		if (passwordExclusion == PasswordExclusion.EXCLUDE_PASSWORD) {
			cv.remove(UserAccountTable.COL_PASSWORD);
		}

		//	@formatter:off
		String whereClause 	= UserAccountTable.COL_WEB_ID + " = ? AND "
			        			+ UserAccountTable.COL_SERVER + " = ?";
		String[] whereArgs = { Integer.toString(webId), server };
			
		int affectedRows = database.update(UserAccountTable.NAME, cv, whereClause, whereArgs);
			
		//	@formatter:on

		return affectedRows;

	}

	public int updateUser(User newUser, int webId, String server) {
		return updateUser(newUser, webId, server, PasswordExclusion.INCLUDE_PASSWORD);
	}

	/**
	 * Marks the specified user as active in the database and deactivates all other users.
	 * 
	 * @param user
	 * @return
	 * @throws UserNotFoundException
	 */
	public void activateUser(int userId) throws UserNotFoundException {

		ContentValues cv = new ContentValues();

		cv.put(UserAccountTable.COL_IS_ACTIVE, ACTIVITY.ACTIVE);

		String whereClause = UserAccountTable.COL_ID + " = ?";
		String[] whereArgs = { Integer.toString(userId) };

		int affectedRows = database.update(UserAccountTable.NAME, cv, whereClause, whereArgs);

		if (affectedRows <= 0) {
			// unable to update anything, the record of the user is not found.
			throw new UserNotFoundException("Failed to activate user with id = " + userId
			        + ", the specified user is not found.");
		} else {
			// deactivate remaining users
			String deactivateWhereClause = UserAccountTable.COL_ID + " != ?";
			database.update(UserAccountTable.NAME, cv, deactivateWhereClause, whereArgs);
		}

	}

	// ============================================================================
	// Query Methods

	/**
	 * Gets the currently active user account saved in this device. If no such use is
	 * found, null is returned.
	 * 
	 * @return a User object containing the data of the active user if it's found,
	 *         otherwise, null.
	 */
	public User getActiveUserAccount() {

		String whereClause = UserAccountTable.COL_IS_ACTIVE + " = ?";
		String[] whereArgs = new String[] { Integer.toString(User.ACTIVITY.ACTIVE) };
		Cursor cursor = database.query(UserAccountTable.NAME, tableColumns, whereClause, whereArgs,
		        null, null, null);

		if (cursor.moveToFirst() && cursor.getCount() > 0) {
			User user = cursorToUser(cursor);
			cursor.close();

			return user;
		} else {
			return null;
		}
	}

	/**
	 * Gets a user by its id.
	 * 
	 * @param id
	 *            the id of the user to get.
	 * @return a User object containing the data of the active user if it's found,
	 *         otherwise, null.
	 */
	public User getUserWithId(int id) {

		String whereClause = UserAccountTable.COL_ID + " = " + id;
		Cursor cursor = database.query(UserAccountTable.NAME, tableColumns, whereClause, null,
		        null, null, null);

		if (cursor.moveToFirst() && cursor.getCount() > 0) {
			User user = cursorToUser(cursor);
			cursor.close();

			return user;
		} else {
			return null;
		}
	}

	/**
	 * 
	 * Gets a user by its web(formalistics) id and server.
	 * 
	 * @param webId
	 *            the web id of the user to get
	 * @param server
	 *            the server of the user to get.
	 * @return a User object containing the data of the active user if it's found,
	 *         otherwise, null.
	 */
	public User getUserWithWebIdAndServer(int webId, String server) {

		//	@formatter:off
		String selection = UserAccountTable.COL_WEB_ID + " = ? AND " + UserAccountTable.COL_SERVER + " = ?";
		String selectionArgs[] = { Integer.toString(webId), server };
		
		Cursor cursor = database.query(
				UserAccountTable.NAME, tableColumns, selection, selectionArgs, 
			    null, null, null);
		
		//	@formatter:on

		boolean userExists = (cursor.moveToFirst() && cursor.getCount() > 0);

		User user = null;

		if (userExists) {
			user = cursorToUser(cursor);
			cursor.close();
		}

		return user;

	}

	// ============================================================================
	// Validation Methods

	public void validateUserForRegistration(User user) throws IncompleteUserValuesException {

		List<String> missedFields = new ArrayList<String>();

		if (user.getWebId() <= 0) {
			missedFields.add("Web ID");
		}

		if (user.getAccountServer() == null || user.getAccountServer().isEmpty()) {
			missedFields.add("Server");
		}

		if (missedFields.size() > 0) {
			throw new IncompleteUserValuesException(missedFields);
		}

	}

	// ============================================================================
	// Exceptions

	public static class IncompleteUserValuesException extends Exception {

		private static final long serialVersionUID = -1687590760970743171L;

		public IncompleteUserValuesException(String[] values) {
			super("The following values must not be blank or null: "
			        + Serializer.serializeArray(values));
		}

		public IncompleteUserValuesException(List<String> values) {
			super("The following values must not be blank or null: "
			        + Serializer.serializeList(values));
		}

	}

	public static class UserNotFoundException extends Exception {

		private static final long serialVersionUID = -3699845269971272053L;

		public UserNotFoundException(String message) {
			super(message);
		}

	}

	// ============================================================================
	// Parser Methods

	/**
	 * Creates a new ContentValues object based on the passed user object. The keys are
	 * based on the columns of the User_Accounts table. The created ContentValues object
	 * does not contain the id of the user object, only the web_id so that the
	 * ContentValues object can be ready for insert.
	 * 
	 * @param user
	 *            the user object where the new ContentValues object will be based on.
	 * @return
	 */
	public ContentValues createCVFromUser(User user) {

		ContentValues cv = new ContentValues();

		// the id is intentionally not set

		cv.put(UserAccountTable.COL_WEB_ID, user.getWebId());
		cv.put(UserAccountTable.COL_EMAIL, user.getEmail());
		cv.put(UserAccountTable.COL_DISPLAY_NAME, user.getDisplayName());
		cv.put(UserAccountTable.COL_IMAGE_URL, user.getImageURL());

		// TODO: add an encryption task here
		cv.put(UserAccountTable.COL_PASSWORD, user.getPassword());

		cv.put(UserAccountTable.COL_SERVER, user.getAccountServer());
		cv.put(UserAccountTable.COL_USER_LEVEL_ID, user.getUserLevel());
		cv.put(UserAccountTable.COL_IS_ACTIVE, user.getActivity());

		return cv;

	}

	private User cursorToUser(Cursor cursor) {

		User user = new User();

		//	@formatter:off
		//	initialize indices		
		int idIndex 			= cursor.getColumnIndexOrThrow(UserAccountTable.COL_ID);
		
		int webIdIndex			= cursor.getColumnIndexOrThrow(UserAccountTable.COL_WEB_ID);
		int emailIndex			= cursor.getColumnIndexOrThrow(UserAccountTable.COL_EMAIL);
		int displayNameIndex	= cursor.getColumnIndexOrThrow(UserAccountTable.COL_DISPLAY_NAME);
		int imageURLIndex		= cursor.getColumnIndexOrThrow(UserAccountTable.COL_IMAGE_URL);
		
		int passwordIndex		= cursor.getColumnIndexOrThrow(UserAccountTable.COL_PASSWORD);
		int serverIndex			= cursor.getColumnIndexOrThrow(UserAccountTable.COL_SERVER);
		int userLevelIndex		= cursor.getColumnIndexOrThrow(UserAccountTable.COL_USER_LEVEL_ID);				

		//	initialize values
		int id				= cursor.getInt(idIndex);
		
		int webId			= cursor.getInt(webIdIndex);
		String email		= cursor.getString(emailIndex);
		String displayName 	= cursor.getString(displayNameIndex);
		String imageURL		= cursor.getString(imageURLIndex);
		
		String password		= cursor.getString(passwordIndex);
		String server		= cursor.getString(serverIndex);
		int userLevel		= cursor.getInt(userLevelIndex);
		
		//	@formatter:on

		// set the user values
		user.setId(id);

		user.setWebId(webId);
		user.setEmail(email);
		user.setDisplayName(displayName);
		user.setImageURL(imageURL);

		user.setPassword(password);
		user.setAccountServer(server);
		user.setUserLevel(userLevel);

		return user;

	}

}
